#!/usr/bin/python2.7                                                            
"""
This module creates a DOM (Document Object Model) Tree - DOMTree.

Copyright (C) 2011 Kevin Fisher

License: MIT

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Author:
    Kevin Fisher <fisher.kevin.g -at- gmail.com>
"""

# =============================================================================

class DOMTree(object):
    """ Implements a DOM (Document Object Model) Tree.

    Since a DOM tree is language neutral, it allows the document to be modified
    more easily and it makes converting a document from one format to another
    much easier.

    Attributes:
        root   The first node in the tree and has no parent.
        curr   A reference marker used to keep track of current location.
    """

    # -------------------------------------------------------------------------

    class Node(object):
        """ A DOM tree node, represents an element in a document.

        Attributes:
            node_type   A string indicating the node's type.
            parent      A reference to the node's parent.
            children    A list of the node's children.
            content     The content of the node.
        """
        def __init__(self, ntype, parent = None, content = None):
            """ Initializes the node's attributes.

            Args:
                ntype     The type of element the node represents.
                parent    A reference to the node's parent
                content   The content of the document element.
            """
            self.node_type   = ntype
            self.parent      = parent
            self.children    = []
            self.content     = content

            if self.parent is not None:
                self.parent.children.append(self)

    # -------------------------------------------------------------------------

    def __init__(self):
        """ Initializes the tree by creating the root node. """
        self.root = self.Node("document")
        self.curr = self.root

    # -------------------------------------------------------------------------

    def insert(self, ntype, content = None, return_to_parent = True):
        """ Inserts a new node as a child of the the current node.

        Args:
            ntype              The type of document node.
            content            The content of the node.
            return_to_parent   If true, the pointer is kept at the new node's
                               parent, otherwise the current pointer is moved
                               to the new node.

        Returns:
            The new node
        """
        node = self.Node(ntype, self.curr, content)

        if not return_to_parent:
            self.curr = node

        return node

    # -------------------------------------------------------------------------

    def upto(self, types = None, to_parent = False):
        """ Looks up the tree for the first occurence of one of the listed
        types of nodes or the root.

        Args:
            types       List of types to stop at.
            to_parent   If true, move to the parent of the upto node.

        Returns:
            The current node after moving up the tree.
        """
        if not types:
            types = ["document"]

        current = self.curr

        while current.parent is not None and not current.node_type in types:
            current = current.parent

        self.curr = current.parent if to_parent else current

        return self.curr

    # -------------------------------------------------------------------------

    def preorder(self, node):
        """ Fetches the nodes from the tree using preorder traversal.

        Args:
            node   The current or starting node, typically this is the root.

        Yields:
            The next node in the traversal.
        """
        yield node

        for child in node.children:
            for element in self.preorder(child):
                yield element

    # -------------------------------------------------------------------------

    def print_tree(self, node = None, space = 0):
        """ Prints the stucture of the tree, use for debugging purposes.

        Args:
            node   What node to print, defaults to root.
            space  How many spaces to prefix to the output.
        """
        current = node or self.root
        print " " * space + "- %s" % current.node_type

        for child in current.children:
            self.print_tree(child, space + 2)

    # -------------------------------------------------------------------------

    @property
    def Root(self):
        """ Gets the root node of the tree. """
        return self.root

    # -------------------------------------------------------------------------

# =============================================================================
# =============================================================================

def main():
    """ Runs a simple test """
    tree = DOMTree()
    tree.insert("A", "NodeA")
    tree.insert("B", "NodeB")
    tree.insert("C", "NodeC")

    tree.print_tree()

    print " *** END TEST *** "

# =============================================================================

if __name__ == "__main__":
    main()


